
package com.cainli.task;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import com.cainli.swingworker.SwingWorker.StateValue;

/**
 * This class is intended to serve as the model for GUI components, like status
 * bars, that display the state of an application's background tasks. {@code
 * TaskMonitor} provides an overview of all the ApplicationContext's Tasks, as
 * well as the state of a single {@code foreground} Task.
 * 
 * <p>
 * The value of {@link #getTasks getTasks()} is a list of all of the {@code
 * Tasks} whose state is not {@link Task#isDone DONE} for all of the
 * ApplicationContext's {@code TaskServices}. In other words: all of the
 * ApplicationContext's background tasks that haven't finished executing. Each
 * time a new TaskService Task is executed it's added to the list; when the Task
 * finishes it's removed. Each time the list changes {@code
 * PropertyChangeListeners} are fired. Applications that wish to create a
 * detailed visualization of all Tasks should monitor the TaskMonitor {@code
 * "tasks"} property.
 * 
 * <p>
 * Users are often only interested in the status of a single <i>foreground</i>
 * task, typically the one associated with GUI element they're working with, or
 * with the most recent command they've issued. The TaskMonitor's
 * PropertyChangeListener is notified each time a property of the
 * {@link #setForegroundTask foregroundTask} changes. Additionally the
 * TaskMonitor fires synthetic PropertyChangeEvents for properties named
 * "pending", "started", and "done" when the corresponding Task {@code state}
 * property changes occur.
 * 
 * <p>
 * TaskMonitor manages a queue of new Tasks. The foregroundTask is automatically
 * set to the first new Task, and when that Task finishes, the next Task in the
 * queue, and so on. Applications can set the foregroundTask explicitly, to
 * better reflect what the user is doing. For example, a tabbed browsing GUI
 * that launched one Task per tab might set the foreground Task each time the
 * user selected a tab. To prevent the foregroundTask property from (ever) being
 * reset automatically, one must set {@link #setAutoUpdateForegroundTask
 * autoUpdateForegroundTask} to false.
 * 
 * <p>
 * This class is not thread-safe. All of its methods must be called on the event
 * dispatching thread (EDT) and all of its listeners will run on the EDT.
 * 
 * 
 * @author Hans Muller (Hans.Muller@Sun.COM)
 * @see ApplicationContext#getTaskServices
 * @see TaskService#getTasks
 * @see TaskService#execute
 */

public class TaskMonitor extends AbstractBean {
    // private final PropertyChangeListener applicationPCL;
    private final PropertyChangeListener taskServicePCL;
    private final PropertyChangeListener taskPCL;
    private final LinkedList<Task> taskQueue;
    private boolean autoUpdateForegroundTask = true;
    private Task foregroundTask = null;

    /**
     * Construct a TaskMonitor.
     */
    public TaskMonitor(TaskService taskService) {
        taskServicePCL = new TaskServicePCL();
        taskPCL = new TaskPCL();
        taskQueue = new LinkedList<Task>();
        // for (TaskService taskService : context.getTaskServices()) {
        taskService.addPropertyChangeListener(taskServicePCL);
        // }
    }

    /**
     * The TaskMonitor's PropertyChangeListeners are fired each time any
     * property of the the {@code foregroundTask} changes. By default this
     * property is set to the first Task to be executed and then, when that Task
     * finishes, reset to the next most recently executed Task. If the {@code
     * autoUpdateForegroundTask} is false, then the foregroundTask property is
     * not reset automatically.
     * 
     * @param foregroundTask
     *            the task whose properties are reflected by this class
     * @see #setAutoUpdateForegroundTask
     * @see #getForegroundTask
     */
    public void setForegroundTask(Task foregroundTask) {
        final Task oldTask = this.foregroundTask;
        if (oldTask != null) {
            oldTask.removePropertyChangeListener(taskPCL);
        }
        this.foregroundTask = foregroundTask;
        Task newTask = this.foregroundTask;
        if (newTask != null) {
            newTask.addPropertyChangeListener(taskPCL);
        }
        firePropertyChange("foregroundTask", oldTask, newTask);
    }

    /**
     * Indicates the {@code Task} whose status the ApplicationContext's GUI
     * wants to be displayed, typically in the main window's status bar.
     * 
     * 
     * @return the value of the foregroundTask property.
     * @see #setForegroundTask
     */
    public Task getForegroundTask() {
        return foregroundTask;
    }

    /**
     * True if the {@code foregroundTask} property should be automatically reset
     * to the oldest Task in the queue when it finishes running.
     * <p>
     * This property is true by default.
     * 
     * @return true if the foregroundTask should be set automatically.
     * @see #setAutoUpdateForegroundTask
     * @see #setForegroundTask
     */
    public boolean getAutoUpdateForegroundTask() {
        return autoUpdateForegroundTask;
    }

    /**
     * True if the {@code foregroundTask} property should be automatically reset
     * to the oldest Task in the queue when it finishes running. An application
     * that wants explicit control over the Task being monitored can set this
     * property to false.
     * <p>
     * This property is true by default.
     * 
     * @param autoUpdateForegroundTask
     *            true if the foregroundTask should be set automatically
     * @see #getAutoUpdateForegroundTask
     */
    public void setAutoUpdateForegroundTask(boolean autoUpdateForegroundTask) {
        boolean oldValue = this.autoUpdateForegroundTask;
        this.autoUpdateForegroundTask = autoUpdateForegroundTask;
        firePropertyChange("autoUpdateForegroundTask", oldValue,
                this.autoUpdateForegroundTask);
    }

    private List<Task> copyTaskQueue() {
        synchronized (taskQueue) {
            if (taskQueue.isEmpty()) {
                return Collections.emptyList();
            } else {
                return new ArrayList<Task>(taskQueue);
            }
        }
    }

    /**
     * All of the Application Tasks whose {@code state} is not {@code DONE}.
     * <p>
     * Each time the list of Tasks changes, a PropertyChangeEvent for the
     * property named "tasks" is fired. Applications that want to monitor all
     * background Tasks should monitor the tasks property.
     * 
     * @return a list of all Tasks that aren't {@code DONE}
     */
    public List<Task> getTasks() {
        return copyTaskQueue();
    }

    /*
     * Called on the EDT, each time a TaskService's list of tasks changes, i.e.
     * each time a new Task is executed and each time a Task's state changes to
     * DONE.
     */
    private void updateTasks(List<Task> oldTasks, List<Task> newTasks) {
        boolean tasksChanged = false; // has the "tasks" property changed?
        List<Task> oldTaskQueue = copyTaskQueue();
        // Remove each oldTask that's not in the newTasks list from taskQueue
        for (Task oldTask : oldTasks) {
            if (!(newTasks.contains(oldTask))) {
                if (taskQueue.remove(oldTask)) {
                    tasksChanged = true;
                }
            }
        }
        // Add each newTask that's not in the oldTasks list to the taskQueue
        for (Task newTask : newTasks) {
            if (!(taskQueue.contains(newTask))) {
                taskQueue.addLast(newTask);
                tasksChanged = true;
            }
        }
        // Remove any tasks that are DONE for the sake of tasksChanged
        Iterator<Task> tasks = taskQueue.iterator();
        while (tasks.hasNext()) {
            Task task = tasks.next();
            if (task.isDone()) {
                tasks.remove();
                tasksChanged = true;
            }
        }
        // Maybe fire the "tasks" PCLs
        if (tasksChanged) {
            List<Task> newTaskQueue = copyTaskQueue();
            firePropertyChange("tasks", oldTaskQueue, newTaskQueue);
        }

        if (autoUpdateForegroundTask && (getForegroundTask() == null)) {
            setForegroundTask(taskQueue.isEmpty() ? null : taskQueue.getLast());
        }
    }

    /*
     * Each time an ApplicationContext TaskService is added or removed, we
     * remove our taskServicePCL from the old ones, add it to the new ones. In a
     * typical application, this will happen infrequently and the number of
     * TaskServices will be small, often just one. This listener runs on the
     * EDT.
     */
    private class ApplicationPCL implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if ("taskServices".equals(propertyName)) {
                List<TaskService> oldList = (List<TaskService>) e.getOldValue();
                List<TaskService> newList = (List<TaskService>) e.getNewValue();
                for (TaskService oldTaskService : oldList) {
                    oldTaskService.removePropertyChangeListener(taskServicePCL);
                }
                for (TaskService newTaskService : newList) {
                    newTaskService.addPropertyChangeListener(taskServicePCL);
                }
            }
        }
    }

    /*
     * Each time a TaskService's list of Tasks (the "tasks" property) changes,
     * update the taskQueue (the "tasks" property) and possibly the
     * foregroundTask property. See updateTasks(). This listener runs on the
     * EDT.
     */
    private class TaskServicePCL implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if ("tasks".equals(propertyName)) {
                List<Task> oldList = (List<Task>) e.getOldValue();
                List<Task> newList = (List<Task>) e.getNewValue();
                updateTasks(oldList, newList);
            }
        }
    }

    /*
     * Each time a property of the foregroundTask that's also a TaskMonitor
     * property changes, update the TaskMonitor's state and fire a TaskMonitor
     * ProprtyChangeEvent. This listener runs on the EDT.
     */
    private class TaskPCL implements PropertyChangeListener {
        private void fireStateChange(Task task, String propertyName) {
            firePropertyChange(new PropertyChangeEvent(task, propertyName,
                    false, true));
        }

        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            Task task = (Task) (e.getSource());
            Object newValue = e.getNewValue();
            if ((task != null) && (task == getForegroundTask())) {
                firePropertyChange(e);
                if ("state".equals(propertyName)) {
                    StateValue newState = (StateValue) (e.getNewValue());
                    switch (newState) {
                    case PENDING:
                        fireStateChange(task, "pending");
                        break;
                    case STARTED:
                        fireStateChange(task, "started");
                        break;
                    case DONE:
                        fireStateChange(task, "done");
                        setForegroundTask(null);
                    }
                }
            }
        }
    }
}
